package com.vr.xuecheng.media.handler.jobhandler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import com.vr.xuecheng.media.model.entity.MediaFiles;
import com.vr.xuecheng.media.model.entity.MediaProcess;
import com.vr.xuecheng.media.model.entity.MediaProcessHistory;
import com.vr.xuecheng.media.service.IMediaFilesService;
import com.vr.xuecheng.media.service.IMediaProcessHistoryService;
import com.vr.xuecheng.media.service.IMediaProcessService;
import com.vr.xuecheng.media.util.MinioUtil;
import com.vr.xuecheng.media.util.Mp4VideoUtil;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author hzh
 * @date 2025/03/15
 * 视频转码任务处理
 */
@Log4j2
@Component
@RequiredArgsConstructor
public class VideoTask {


    int processorCount = Runtime.getRuntime().availableProcessors();

    private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            processorCount + 1,
            processorCount * 2,
            60L,
            java.util.concurrent.TimeUnit.SECONDS,
            new java.util.concurrent.ArrayBlockingQueue<>(100),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );
    @Value("${videoprocess.ffmpeg-path}")
    private String ffmpegPath;
    @Value("${minio.bucket-name.video-name}")
    private String videoBucket;
    private final MinioUtil minioUtil;
    private final IMediaFilesService mediaFilesService;
    private final IMediaProcessService mediaProcessService;
    private final IMediaProcessHistoryService mediaProcessHistoryService;

    @XxlJob("videoJobTask")
    @Transactional
    public void videoJobTask() {
        // 分片参数
        // 序号，从0开始
        int shardIndex = XxlJobHelper.getShardIndex();
        // 执行器分片的数量
        int shardTotal = XxlJobHelper.getShardTotal();
        // 拿到所执行的任务，每次处理的任务不能超过核心数

        List<MediaProcess> mediaProcessList = mediaProcessService.getMediaProcessList(shardIndex, shardTotal, 2);
        int taskCount = mediaProcessList.size();
        log.debug("待处理的任务数有:{}", taskCount);
        if (taskCount == 0) {
            log.debug("没有需要处理的任务");
            return;
        }
        // 计数器
        CountDownLatch countDownLatch = new CountDownLatch(taskCount);

        // 开启线程并行处理任务
        mediaProcessList.forEach(mediaProcess -> {
            threadPoolExecutor.execute(() -> {
                // 创建临时文件
                File aviTempFile = null;
                File mp4TempFile = null;
                try {
                    // 开始任务
                    // 为了避免同一个任务被多次执行
                    boolean isGetTask = mediaProcessService.startTask(mediaProcess.getId());
                    if (!isGetTask) {
                        return;
                    }
                    // 创建临时文件
                    MediaProcess failMediaProcess = BeanUtil.toBean(mediaProcess, MediaProcess.class);
                    try {
                        aviTempFile = File.createTempFile("avi", ".avi");
                        mp4TempFile = File.createTempFile("mp4", ".mp4");
                    } catch (IOException e) {
                        log.error("创建临时文件失败");
                        // 将任务更新成失败
                        mediaProcessService.updateMediaProcessFinishStatus(
                                failMediaProcess
                                        .setStatus("3")
                                        .setFailCount(failMediaProcess.getFailCount() + 1)
                                        .setErrormsg("创建临时文件失败"));
                        return;
                    }
                    // 将minio的mp4文件下载到本地(也就是服务上)
                    InputStream inputStream = minioUtil.downloadFile(mediaProcess.getFilePath(), videoBucket);
                    if (inputStream == null) {
                        log.error("下载文件失败");
                        // 将任务更新成失败
                        mediaProcessService.updateMediaProcessFinishStatus(
                                failMediaProcess
                                        .setStatus("3")
                                        .setFailCount(failMediaProcess.getFailCount() + 1)
                                        .setErrormsg("下载文件失败或者文件不存在"));
                        return;
                    }
                    File orginalFile = FileUtil.writeFromStream(inputStream, aviTempFile);
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    if (orginalFile == null) {
                        log.error("下载文件失败");
                        // 将任务更新成失败
                        mediaProcessService.updateMediaProcessFinishStatus(
                                failMediaProcess
                                        .setStatus("3")
                                        .setFailCount(failMediaProcess.getFailCount() + 1)
                                        .setErrormsg("下载文件失败"));
                        return;
                    }
                    // ffmpeg将avi转码成mp4
                    String transResult = "";
                    try {
                        Mp4VideoUtil mp4VideoUtil = new Mp4VideoUtil(ffmpegPath, orginalFile.getAbsolutePath(), mp4TempFile.getName(), mp4TempFile.getAbsolutePath());
                        transResult = mp4VideoUtil.generateMp4();
                    } catch (Exception e) {
                        log.error("处理视频文件:{},出错:{}", mp4TempFile.getAbsolutePath(), e.getMessage());
                    }
                    if (!"success".equals(transResult)) {
                        log.error("处理视频失败,视频地址{},出错:{}", videoBucket + mediaProcess.getFilePath(), transResult);
                        mediaProcessService.updateMediaProcessFinishStatus(
                                failMediaProcess
                                        .setStatus("3")
                                        .setFailCount(failMediaProcess.getFailCount() + 1)
                                        .setErrormsg(transResult));
                    }

                    // avi视频转码mp4成功
                    // 上传到minio里
                    String uploadObjectName = mediaProcess.getFilePath().substring(0, mediaProcess.getFilePath().lastIndexOf(".")) + ".mp4";
                    try {
                        BufferedInputStream waitInputStream = FileUtil.getInputStream(mp4TempFile);
                        boolean isUploadMinio = minioUtil.uploadFile(waitInputStream, mp4TempFile.length(), uploadObjectName, videoBucket, "video/mp4");
                        if (!isUploadMinio) {
                            log.error("上传文件失败");
                            mediaProcessService.updateMediaProcessFinishStatus(
                                    failMediaProcess
                                            .setStatus("3")
                                            .setFailCount(failMediaProcess.getFailCount() + 1)
                                            .setErrormsg("上传文件失败"));
                            return;
                        }
                        waitInputStream.close();

                        // 更新media_process里的信息
                        MediaProcess mediaProcessOfUpdate = failMediaProcess
                                .setId(mediaProcess.getId())
                                .setBucket(videoBucket)
                                .setFileId(mediaProcess.getFileId())
                                .setStatus("2")
                                .setUrl("/" + videoBucket + "/" + uploadObjectName)
                                .setFilePath(uploadObjectName)
                                .setFinishDate(LocalDateTime.now())
                                .setFilename(mediaProcess.getFilename().substring(0, mediaProcess.getFilename().lastIndexOf(".")) + ".mp4");
                        mediaProcessService.updateById(mediaProcessOfUpdate);
                        // 更新media_files里的信息
                        mediaFilesService.lambdaUpdate().eq(MediaFiles::getFileId, mediaProcess.getFileId())
                                .set(MediaFiles::getUrl, mediaProcessOfUpdate.getUrl())
                                .set(MediaFiles::getFilePath, mediaProcessOfUpdate.getFilePath())
                                .set(MediaFiles::getFilename, mediaProcessOfUpdate.getFilename())
                                .set(MediaFiles::getFileId, mediaProcessOfUpdate.getFileId())
                                .set(MediaFiles::getFileSize, mp4TempFile.length())
                                .set(MediaFiles::getBucket, mediaProcessOfUpdate.getBucket()).update();
                        // 最后成功将信息新增到media_process_history里并删除media_process里的信息
                        MediaProcessHistory mediaProcessHistoryOfSuccess = BeanUtil.toBean(mediaProcessOfUpdate, MediaProcessHistory.class);
                        mediaProcessHistoryService.save(mediaProcessHistoryOfSuccess);
                        mediaProcessService.removeById(mediaProcess.getId());


                        String originalFilePath = mediaProcess.getFilePath().substring(0, mediaProcess.getFilePath().lastIndexOf(".")) + ".avi";
                        minioUtil.deleteFile(originalFilePath, videoBucket);
                    } catch (Exception e) {
                        log.error("上传失败或者入库失败,视频地址:{},错误:{}", videoBucket + "/" + uploadObjectName, e.getMessage());
                        mediaProcessService.updateMediaProcessFinishStatus(
                                failMediaProcess
                                        .setStatus("3")
                                        .setFailCount(failMediaProcess.getFailCount() + 1)
                                        .setErrormsg("处理后视频上传或入库失败"));
                    }
                } finally {
                    // 任务不管是成功还是失败都都计数
                    countDownLatch.countDown();
                    FileUtil.del(aviTempFile);
                    FileUtil.del(mp4TempFile);

                }
            });
        });
        try {
            boolean await = countDownLatch.await(30L, TimeUnit.MINUTES);
            if (!await) {
                log.error("任务执行超时");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

}
